{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use with `vscode` and its [`Data Wrangler`](https://code.visualstudio.com/docs/datascience/data-wrangler)+`Jupyter` extension. Check in `JUPYTER` Bottom Tab.\n",
    "\n",
    "*known bug:* if **keyname** has special char, update into globals **varname** space will crash vscode, which cause memory and CPU usage get abnormally higher.\n",
    "\n",
    "**YOU MUST RESTART VSCODE** after editing source code when that crash happens."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "import numpy as np\n",
    "try:\n",
    "    _DIR_CURRENT = os.path.dirname(os.path.abspath(__file__))\n",
    "except NameError:\n",
    "    _DIR_CURRENT = os.getcwd()\n",
    "_DIR_ROOT = os.path.dirname(_DIR_CURRENT)\n",
    "sys.path.insert(0, _DIR_ROOT)\n",
    "from src.mocap_wrapper.lib.data_viewer import convert_npt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load as numpy.lib.npyio.NpzFile from /home/n/document/code/mocap/output/finger_dance/mocap_finger_dance.npz\n",
      "{\n",
      "  \"smplx_gvhmr_person0_0_bbox\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person1_0_bbox\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person0_0_body_pose\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      21,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person0_0_betas\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      10\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person0_0_global_orient\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person0_0_transl\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      3\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_cam_person0_0_global_orient\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_cam_person0_0_transl\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      3\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person1_0_body_pose\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      21,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person1_0_betas\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      10\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person1_0_global_orient\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_person1_0_transl\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      3\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_cam_person1_0_global_orient\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      4\n",
      "    ]\n",
      "  },\n",
      "  \"smplx_gvhmr_cam_person1_0_transl\": {\n",
      "    \"type\": \"numpy.ndarray\",\n",
      "    \"dtype\": \"numpy.dtypes.Float32DType\",\n",
      "    \"shape\": [\n",
      "      149,\n",
      "      3\n",
      "    ]\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "_GLOBAL = convert_npt(files=[\n",
    "    # '/home/n/document/code/mocap/output/wave_hands/mocap_wave_hands.npz',\n",
    "    # '/home/n/document/code/mocap/output/finger_dance/hmr4d_results_0.pt',\n",
    "    # '/home/n/document/code/mocap/output/my_hand/mocap_my_hand.npz',\n",
    "    '/home/n/document/code/mocap/output/finger_dance/mocap_finger_dance.npz',\n",
    "    # '/home/n/document/code/mocap/output/finger_dance/preprocess/slam_results.pt',\n",
    "    # '/home/n/document/code/mocap/output/finger_dance/preprocess/vit_features_0.pt',\n",
    "    # '/home/n/document/code/mocap/output/finger_dance/preprocess/vitpose_0.pt',\n",
    "],\n",
    "save=False,\n",
    "Print=True,\n",
    ")\n",
    "globals().update(_GLOBAL)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.graph_objects as go\n",
    "import plotly.express as px\n",
    "from ipywidgets import Dropdown, IntSlider, VBox, HBox, Output, Checkbox\n",
    "import ipywidgets as widgets\n",
    "\n",
    "def create_interactive_plot(data, title=\"\"):\n",
    "    \"\"\"\n",
    "    创建交互式多维数据可视化\n",
    "    \n",
    "    Parameters:\n",
    "    data: numpy array, 任意形状的数据\n",
    "    title: str, 图表标题\n",
    "    \"\"\"\n",
    "    if not isinstance(data, np.ndarray):\n",
    "        data = np.array(data)\n",
    "    \n",
    "    shape = data.shape\n",
    "    ndim = len(shape)\n",
    "    \n",
    "    # 创建输出容器\n",
    "    output = Output()\n",
    "    \n",
    "    # Y轴维度选择下拉框\n",
    "    y_axis_dropdown = Dropdown(\n",
    "        options=[(f'维度 {i} (size: {shape[i]})', i) for i in range(ndim)],\n",
    "        value=0,\n",
    "        description='Y轴:',\n",
    "        style={'description_width': 'initial'}\n",
    "    )\n",
    "    \n",
    "    # 滑块容器\n",
    "    sliders_container = VBox([])\n",
    "    \n",
    "    # 为其他维度创建索引选择滑块和复选框\n",
    "    index_sliders = {}\n",
    "    show_all_checkboxes = {}\n",
    "    \n",
    "    def create_sliders_for_dimension(y_dim):\n",
    "        \"\"\"为指定的Y轴维度创建滑块和复选框\"\"\"\n",
    "        # 清除现有控件\n",
    "        for slider in index_sliders.values():\n",
    "            slider.close()\n",
    "        for checkbox in show_all_checkboxes.values():\n",
    "            checkbox.close()\n",
    "        index_sliders.clear()\n",
    "        show_all_checkboxes.clear()\n",
    "        \n",
    "        # 创建新的控件（除了Y轴维度）\n",
    "        control_widgets = []\n",
    "        for i in range(ndim):\n",
    "            if i != y_dim:\n",
    "                # 创建滑块\n",
    "                slider = IntSlider(\n",
    "                    value=0,\n",
    "                    min=0,\n",
    "                    max=shape[i]-1,\n",
    "                    step=1,\n",
    "                    description=f'索引:',\n",
    "                    style={'description_width': 'initial'},\n",
    "                    layout=widgets.Layout(width='300px')\n",
    "                )\n",
    "                \n",
    "                # 创建复选框\n",
    "                checkbox = Checkbox(\n",
    "                    value=True,\n",
    "                    description=f'固定维度{i}',\n",
    "                    style={'description_width': 'initial'},\n",
    "                    layout=widgets.Layout(width='100px')\n",
    "                )\n",
    "                \n",
    "                # 绑定事件\n",
    "                slider.observe(update_plot, names='value')\n",
    "                checkbox.observe(update_plot, names='value')\n",
    "                \n",
    "                # 存储引用\n",
    "                index_sliders[i] = slider\n",
    "                show_all_checkboxes[i] = checkbox\n",
    "                \n",
    "                # 创建水平布局：滑块 + 复选框\n",
    "                control_row = HBox([checkbox,slider])\n",
    "                control_widgets.append(control_row)\n",
    "        \n",
    "        # 更新滑块容器\n",
    "        sliders_container.children = control_widgets\n",
    "    \n",
    "    def update_sliders(change=None):\n",
    "        \"\"\"当Y轴维度改变时，更新滑块\"\"\"\n",
    "        y_dim = y_axis_dropdown.value\n",
    "        create_sliders_for_dimension(y_dim)\n",
    "        update_plot()\n",
    "    \n",
    "    def update_plot(change=None):\n",
    "        \"\"\"更新图表\"\"\"\n",
    "        with output:\n",
    "            output.clear_output(wait=True)\n",
    "            \n",
    "            y_dim = y_axis_dropdown.value\n",
    "            \n",
    "            # 创建图表\n",
    "            fig = go.Figure()\n",
    "            \n",
    "            # 获取需要显示全部数据的维度\n",
    "            show_all_dims = []\n",
    "            fixed_indices = {}\n",
    "            \n",
    "            for dim, checkbox in show_all_checkboxes.items():\n",
    "                if not checkbox.value:\n",
    "                    show_all_dims.append(dim)\n",
    "                else:\n",
    "                    fixed_indices[dim] = index_sliders[dim].value\n",
    "            \n",
    "            try:\n",
    "                if not show_all_dims:\n",
    "                    # 没有维度需要显示全部，按原来的方式处理\n",
    "                    indices = [slice(None)] * ndim\n",
    "                    indices[y_dim] = slice(None)\n",
    "                    \n",
    "                    for dim, value in fixed_indices.items():\n",
    "                        indices[dim] = value\n",
    "                    \n",
    "                    filtered_data = data[tuple(indices)]\n",
    "                    \n",
    "                    if filtered_data.ndim > 1:\n",
    "                        filtered_data = filtered_data.flatten()\n",
    "                    \n",
    "                    x_values = np.arange(len(filtered_data))\n",
    "                    \n",
    "                    fig.add_trace(go.Scatter(\n",
    "                        x=x_values,\n",
    "                        y=filtered_data,\n",
    "                        mode='lines+markers',\n",
    "                        name='数据',\n",
    "                        line=dict(width=2),\n",
    "                        marker=dict(size=4)\n",
    "                    ))\n",
    "                    \n",
    "                    title_info = f\"\"\n",
    "                    if fixed_indices:\n",
    "                        index_info = [f\"维度{dim}[{value}]\" for dim, value in fixed_indices.items()]\n",
    "                        title_info += f\"固定: {', '.join(index_info)}\"\n",
    "                \n",
    "                else:\n",
    "                    # 有维度需要显示全部\n",
    "                    # 生成颜色\n",
    "                    colors = px.colors.qualitative.Set1\n",
    "                    color_idx = 0\n",
    "                    \n",
    "                    # 计算需要绘制的线条数量\n",
    "                    total_lines = 1\n",
    "                    for dim in show_all_dims:\n",
    "                        total_lines *= shape[dim]\n",
    "                    \n",
    "                    # 限制最大线条数量以避免性能问题\n",
    "                    if total_lines > 50:\n",
    "                        print(f\"警告: 需要绘制 {total_lines} 条线，可能会影响性能\")\n",
    "                    \n",
    "                    # 生成所有需要显示的索引组合\n",
    "                    from itertools import product\n",
    "                    \n",
    "                    show_all_ranges = [range(shape[dim]) for dim in show_all_dims]\n",
    "                    \n",
    "                    for combo in product(*show_all_ranges):\n",
    "                        # 构建索引\n",
    "                        indices = [slice(None)] * ndim\n",
    "                        indices[y_dim] = slice(None)\n",
    "                        \n",
    "                        # 设置固定维度的索引\n",
    "                        for dim, value in fixed_indices.items():\n",
    "                            indices[dim] = value\n",
    "                        \n",
    "                        # 设置显示全部维度的当前索引\n",
    "                        for i, dim in enumerate(show_all_dims):\n",
    "                            indices[dim] = combo[i]\n",
    "                        \n",
    "                        filtered_data = data[tuple(indices)]\n",
    "                        \n",
    "                        if filtered_data.ndim > 1:\n",
    "                            filtered_data = filtered_data.flatten()\n",
    "                        \n",
    "                        x_values = np.arange(len(filtered_data))\n",
    "                        \n",
    "                        # 生成线条标签\n",
    "                        combo_labels = [f\"维度{dim}[{combo[i]}]\" for i, dim in enumerate(show_all_dims)]\n",
    "                        line_name = ', '.join(combo_labels)\n",
    "                        \n",
    "                        fig.add_trace(go.Scatter(\n",
    "                            x=x_values,\n",
    "                            y=filtered_data,\n",
    "                            mode='lines+markers',\n",
    "                            name=line_name,\n",
    "                            line=dict(\n",
    "                                width=2,\n",
    "                                color=colors[color_idx % len(colors)]\n",
    "                            ),\n",
    "                            marker=dict(size=3)\n",
    "                        ))\n",
    "                        \n",
    "                        color_idx += 1\n",
    "                    \n",
    "                    title_info = []\n",
    "                    # if show_all_dims:\n",
    "                    #     show_all_info = [f\"维度{dim}\" for dim in show_all_dims]\n",
    "                    #     title_info.append(f\"显示全部: {', '.join(show_all_info)}\")\n",
    "                    if fixed_indices:\n",
    "                        fixed_info = [f\"维度{dim}[{value}]\" for dim, value in fixed_indices.items()]\n",
    "                        title_info.append(f\"\\t固定: {', '.join(fixed_info)}\")\n",
    "                    title_info = ', '.join(title_info)\n",
    "                \n",
    "                fig.update_layout(\n",
    "                    title=f\"{title}<br><sub>{title_info}</sub>\",\n",
    "                    xaxis_title=\"索引\",\n",
    "                    yaxis_title=f\"维度{y_dim}的值\",\n",
    "                    hovermode='x unified',\n",
    "                    template='plotly_white',\n",
    "                    height=600,\n",
    "                    legend=dict(\n",
    "                        yanchor=\"top\",\n",
    "                        y=0.99,\n",
    "                        xanchor=\"left\",\n",
    "                        x=1.01\n",
    "                    )\n",
    "                )\n",
    "                \n",
    "                # 添加数据信息\n",
    "                info_text = f\"数据形状: {shape}\"\n",
    "                if show_all_dims:\n",
    "                    info_text += f\"<br>显示全部维度: {show_all_dims}\"\n",
    "                \n",
    "                fig.add_annotation(\n",
    "                    text=info_text,\n",
    "                    xref=\"paper\", yref=\"paper\",\n",
    "                    x=0.02, y=0.98,\n",
    "                    showarrow=False,\n",
    "                    bgcolor=\"rgba(255,255,255,0.8)\",\n",
    "                    bordercolor=\"gray\",\n",
    "                    borderwidth=1\n",
    "                )\n",
    "                \n",
    "                fig.show()\n",
    "                \n",
    "            except Exception as e:\n",
    "                print(f\"绘图错误: {e}\")\n",
    "                import traceback\n",
    "                traceback.print_exc()\n",
    "    \n",
    "    # 初始化\n",
    "    create_sliders_for_dimension(y_axis_dropdown.value)\n",
    "    \n",
    "    # 绑定事件\n",
    "    y_axis_dropdown.observe(update_sliders, names='value')\n",
    "    \n",
    "    # 创建控制面板\n",
    "    controls_box = VBox([\n",
    "        y_axis_dropdown,\n",
    "        sliders_container\n",
    "    ])\n",
    "    \n",
    "    # 显示界面\n",
    "    display(controls_box)\n",
    "    display(output)\n",
    "    update_plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "8ede14bf4ce647d7887843df68f24e27",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f854941da7a84955b22c087ffc682d9c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Dropdown(description='Y轴:', options=(('维度 0 (size: 215)', 0), ('维度 1 (size: 15)', 1), ('维度 2 (s…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "05264692740e441aafcab61eb3ac76db",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e9932914057a448d9d0e8375827060b2",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Dropdown(description='Y轴:', options=(('维度 0 (size: 215)', 0), ('维度 1 (size: 4)', 1)), style=Des…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ae62e4e8c4d240a1a19a474045e70427",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# use `ctrl+space` to auto-complete keyname\n",
    "_datas = [\n",
    "    _GLOBAL['smplx_wilor_hand0R_0_hand_pose'],\n",
    "    _GLOBAL['smplx_wilor_hand0R_0_global_orient'],\n",
    "]\n",
    "for d in _datas:\n",
    "    # d = d*180/np.pi\n",
    "    create_interactive_plot(d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# from src.mocap_wrapper.run.lib import savez\n",
    "# savez('/home/n/document/code/mocap/output/wave_hands/mocap.npz', {\n",
    "#     'smplx;wilor;hand1R;0;global_orient':_GLOBAL['smplx_wilor_hand0R_0_global_orient'],\n",
    "# })"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mocap",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
